#ifndef __M_DB_H__
#define __M_DB_H__
#include"util.hpp"
#include"logger.hpp"
#include<pthread.h>
#include<mutex>
#include<assert.h>
class user_table
{
public:
    user_table(const std::string &host, const std::string &usrname,const std::string&password,
    const std::string& dbname,uint16_t port=3306)
    {
        _mysql=mysql_util::mysql_create(host,usrname,password,dbname,port);
        assert(_mysql!=nullptr);//如果为真说明创建失败，直接退出
    }
    ~user_table()
    {
        mysql_util::mysql_destroy(_mysql);
        _mysql=nullptr;
    }
    bool insert(Json::Value &user)//注册时新增用户
    {   
        //如果能根据用户名找到，说明已经存在了---甚至可以不用写，因为增删改，在创建table的时候就已经设置了同一用户名只能存在一次了
        //有unique key约束，如果已经存在sql里就已经会自己报错了
        // Json::Value val;
        // if(select_by_name(user["username"].asCString(),val)) 
        // {
        //     DLOG("user:%s is already exists",user["username"].asCString());
        //     return false;
        // }

#define INSERT_USER "insert user values(null,'%s',password('%s'),1000,0,0);"
        char sql[4096] ={0};
        //这里必须进行校验
        if(user["username"].isNull()||user["password"].isNull()) {
            ILOG("USERNAME OR PASSWORD LOSE");
            return false;
        }
        sprintf(sql,INSERT_USER,user["username"].asCString(),user["password"].asCString());
        bool ret=mysql_util::mysql_exec(_mysql,sql);
        if(!ret)
        {
            ELOG("insert user info failed\n");
            return false;
        }
        return true;

    }
    bool login(Json::Value& user)//登陆验证，并返回详细的用户信息
    //以用户名和密码共同作为查询过滤条件，查询到数据则表示用户名密码一致，没有信息则用户名密码错误
    {
#define LOGIN_USER "select id, score,total_count,win_count from user where username='%s' and password=password('%s');"//密码这样写是加密
        //这个其实就是查询
        char sql[4096] ={0};
        //这里必须进行校验
        if(user["username"].isNull()||user["password"].isNull()) {
            ILOG("USERNAME OR PASSWORD LOSE");
            return false;
        }
        sprintf(sql,LOGIN_USER,user["username"].asCString(),user["password"].asCString());
        MYSQL_RES *res=nullptr;
        //需要锁
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret=mysql_util::mysql_exec(_mysql,sql);
            if(!ret)
            {
                ELOG("login failed\n");
                return false;
            }
            //按理来说要么有数据，要么没数据，就算有数据，也只有一条数据，不可能存在有多个相同的用户信息的情况
            //进行查询之后要保存结果集
            res= mysql_store_result(_mysql);
            if(!res)
            {
            DLOG("have no user info");
            return false;
            }
        }
        //此时已经拿到结果了，不会了丢了，所有这里是不用加锁了，
        int row_num=mysql_num_rows(res);//根据结果集来获取条目数
        if(row_num!=1)//说明不是对的
        {
            DLOG("user info queried not unique or not found");
            return false;
        }
        //能走到这里说明能拿到那一条数据
        MYSQL_ROW row =mysql_fetch_row(res);
        //把它里面的数据都拿出来
        user["id"]=std::stol(row[0]);
        user["score"]=std::stol(row[1]);
        user["total_count"]=std::stol(row[2]);
        user["win_count"]=std::stol(row[3]);

        //释放结果集
        mysql_free_result(res);
        ILOG("login success!");
        return true;
    }
    bool select_by_name(const std::string &name,Json::Value& user)//通过用户名获取用户信息
    {
#define SELECT_BY_NAME "select id, score,total_count,win_count from user where username='%s';"
        char sql[4096] ={0};
        sprintf(sql,SELECT_BY_NAME,name.c_str());
        MYSQL_RES *res=nullptr;
        //需要锁
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret=mysql_util::mysql_exec(_mysql,sql);
            if(!ret)
            {
                ELOG("select name failed\n");
                return false;
            }
            res=mysql_store_result(_mysql);
            if(!res)
            {
            DLOG("have no user info");
            return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if(row_num!=1)//说明不是对的
        {
            DLOG("user info queried not unique");
            return false;
        }
        MYSQL_ROW row=mysql_fetch_row(res);
        user["username"]=name;
        user["id"]=std::stol(row[0]);
        user["score"]=std::stol(row[1]);
        user["total_count"]=std::stol(row[2]);
        user["win_count"]=std::stol(row[3]);

        mysql_free_result(res);
        ILOG("select_by_name success!");
        return true;


    }
    bool select_by_id(int id,Json::Value &user)//通过用户id获取用户信息
    {
#define SELECT_BY_ID "select username, score,total_count,win_count from user where id='%d';"
        char sql[4096] ={0};
        sprintf(sql,SELECT_BY_ID,id);
        MYSQL_RES *res=nullptr;
        //需要锁
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret=mysql_util::mysql_exec(_mysql,sql);
            if(!ret)
            {
                ELOG("select by id failed\n");
                return false;
            }
            res=mysql_store_result(_mysql);
            if(!res)
            {
            DLOG("have no user info");
            return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if(row_num!=1)//说明不是对的
        {
            DLOG("user info queried not unique or not found");
            return false;
        }
        MYSQL_ROW row=mysql_fetch_row(res);
        user["username"]=row[0];//因为在创建数据库的时候确定了username的类型，所以这里是不能转化成long类型的，username就是字符串
        user["id"]=id;
        user["score"]=std::stol(row[1]);
        user["total_count"]=std::stol(row[2]);
        user["win_count"]=std::stol(row[3]);

        mysql_free_result(res);
        ILOG("select_by_id success!");
        return true;
 
    }
    bool win(uint64_t id)//胜利时天梯分数增加，战斗场次增加，胜利场次增加
    {
#define WIN "update user set score=score+30,total_count=total_count+1,win_count=win_count+1 where id=%ld"
        char sql[4096]={0};
        sprintf(sql,WIN,id);
        bool ret=mysql_util::mysql_exec(_mysql,sql);
        if(ret==false)
        {
            DLOG("update win data failed");
            return false;
        }
        return true;

    }
    bool lose(uint64_t id)//失败时天梯分数减少，战斗场次增加，其他不变
    {
#define LOSE "update user set score=score-30,total_count=total_count+1 where id=%ld"
        char sql[4096]={0};
        sprintf(sql,LOSE,id);
        bool ret=mysql_util::mysql_exec(_mysql,sql);
        if(ret==false)
        {
            DLOG("update lose data failed");
            return false;
        }
        return true;
    }

private:
    MYSQL *_mysql;//mysql操作句柄
    std::mutex _mutex;//互斥锁保护数据库的访问操作---增、改只要语句执行成功就行了，mysql_exec调用的mysql_query是线程安全的，不用担心多线程的线程安全问题
    //但是查询不一样，我们的执行语句和把结果集保存到本地，是两步操作，他们两个接口拎出来都是原子的，但是两个一起用，就不是了
    //---执行完查询语句的时候，还没来得及把结果保存到本地，这时如果有其他的用户又来执行其他的语句，就会把结果弄没了，就出问题了，获取结果就阻塞在那里了
    //因此需要锁来加锁
};
#endif
